////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Filename:    RoninGuts.h                                                  //
//  Developer:   George Chapman                                               //
//  Create date: 01/10/2005                                                   //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Description: This is the class implementation for the slot gut for the    //
//               Australian Slot machine called Ronin.                        //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//        Copyright (c) 2004 - Realtime Gaming.  All rights reserved.         //
////////////////////////////////////////////////////////////////////////////////

#include "Roninguts.h"

// MVarela, 09/06/1007, 7.1.0, removed hardcoded probability arrays for deciding feature game 
// mode and number of free spins, replacing them by database probability tables.

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::RoninGuts()   
///
/// This is the default constructor for the class.
////////////////////////////////////////////////////////////////////////////
RoninGuts::RoninGuts() 
{
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::~RoninGuts()   
///
/// This is the destructor for the class.
////////////////////////////////////////////////////////////////////////////
RoninGuts::~RoninGuts() 
{
}
    
////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::classifyStops()   
///
/// @param   stops      The array of stop positions for the line.
/// @param   bet        The value of the line bet.
/// @param   line       The index of the line.
///
/// @return	 uint8      A payout from the SpinResult enumeration as a uint8.    
///
/// This helper method is used to calculate the payout on a line.
////////////////////////////////////////////////////////////////////////////
void
RoninGuts::classifyStops(SpinLine* spinline, uint8* stops,uint8 bet, uint8 line, uint8& rtnresult, uint8& multiplier)
{
	SpinResult result             = EVT_LOSER; // spin result :)
	uint8      testIcon           = BLANK;     // Icon at current position
	uint8      iconThatIsNotRonin = BLANK;     // Holder for an Icon that is NOT Ronin
	bool8      bHasRonin          = false;     // Has a WILD icon (a ronin icon) in the sequence
    bool8      bHasKoku           = false;     // Was there a Koku substitution symbol?
    bool8      bFirstIconIsRonin  = false;     // Ronin in the first spot means it can stand alone OR substitute
    uint8      consecutiveRonins  = 0;         // The number of consecutive ronins

    // By default everything pays at a x1 multiplier
    multiplier = 1;

	//Now, go through the rest, until the streak is over
	for ( uint8 i = 0; (i < getReelCount()); i++ )
    {
		testIcon = getIcon(getGameModeId(), i, stops[i])->iconid;

        // Check to see if it is Ronin (we bump the count and continue looking)
        if ( testIcon == RONIN )
        {
            if ( i == 0 )
            {
                bFirstIconIsRonin = true;
            }

            bHasRonin = true;

            if ( bFirstIconIsRonin && iconThatIsNotRonin == BLANK )
            {
                consecutiveRonins++;
            }
        }
        else if ( testIcon == KOKU )
        {
            if ( bFirstIconIsRonin && iconThatIsNotRonin == BLANK )
            {
                consecutiveRonins++;
            }

            bHasKoku = true;
        }
        else
        {
            // If we haven't found our first non Ronin or Koku icon, store it off.
            if ( iconThatIsNotRonin == BLANK )
            {
                iconThatIsNotRonin = testIcon;
            }
            // If we have found our fist non ronin icon, and this icon was not ronin
            // and it is not the same as the first non Ronin icon, then our evaluation stops.
            else if ( iconThatIsNotRonin != testIcon )
            {
                break;
            }
        }
    }

	// All other Icons EVAL
	switch(iconThatIsNotRonin)
	{
		case NINE:
			if ( i == 5 )
			{
                result = EVT_FIVE_NINE;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_NINE;
			}
			else if (i == 3)
            {
                result = EVT_THREE_NINE;
			}
			break;
 
		case TEN:
			if ( i == 5 )
			{
                result = EVT_FIVE_TEN;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_TEN;
			}
			else if (i == 3)
            {
                result = EVT_THREE_TEN;
			}
			break;

        case JACK:
			if ( i == 5 )
			{
                result = EVT_FIVE_JACK;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_JACK;
			}
			else if (i == 3)
            {
                result = EVT_THREE_JACK;
			}
			break;

        case QUEEN:
			if ( i == 5 )
			{
                result = EVT_FIVE_QUEEN;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_QUEEN;
			}
			else if (i == 3)
            {
                result = EVT_THREE_QUEEN;
			}
            break;		
		
        case KING:
			if ( i == 5 )
			{
                result = EVT_FIVE_KING;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_KING;
			}
			else if (i == 3)
            {
                result = EVT_THREE_KING;
			}
			break;

        case ACE:
			if ( i == 5 )
			{
                result = EVT_FIVE_ACE;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_ACE;
			}
			else if (i == 3)
            {
                result = EVT_THREE_ACE;
			}
            break;


        case FLAG:
			if ( i == 5 )
			{
                result = EVT_FIVE_FLAG;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_FLAG;
			}
			else if (i == 3)
            {
                result = EVT_THREE_FLAG;
			}
			else if (i == 2)
            {
                result = EVT_TWO_FLAG;
			}
			break;

        case HELM:
			if ( i == 5 )
			{
                result = EVT_FIVE_HELM;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_HELM;
			}
			else if (i == 3)
            {
                result = EVT_THREE_HELM;
			}
			else if (i == 2)
            {
                result = EVT_TWO_HELM;
			}
			break;

        case HORSE:
			if ( i == 5 )
			{
                result = EVT_FIVE_HORSE;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_HORSE;
			}
			else if (i == 3)
            {
                result = EVT_THREE_HORSE;
			}
			else if (i == 2)
            {
                result = EVT_TWO_HORSE;
			}
			break;

        case CASTLE:
			if ( i == 5 )
			{
                result = EVT_FIVE_CASTLE;
			}
			else if (i == 4)
            {
                result = EVT_FOUR_CASTLE;
			}
			else if (i == 3)
            {
                result = EVT_THREE_CASTLE;
			}
			else if (i == 2)
            {
                result = EVT_TWO_CASTLE;
			}
			break;

		case BLANK:
			break;

        // Koku should never be the eval icon
        case KOKU:
            ASSERT(false);
            break;
        
        // Some unknown symbol is on the reel!
        default:
            ASSERT(false);
            break;
	}

    // Now, if the player started with Ronin
    // and ended with some other symbol, then
    // it is possible the Ronin are worth more 
    // by themselves then they are in combination
    // with another symbol.  Check to see
    // if that is the case and if so, alter the result.
    if ( consecutiveRonins > 1 && bFirstIconIsRonin )
    {
        SpinResult roninResult = EVT_LOSER;
	    if (consecutiveRonins == 5 )
	    {
		    roninResult = EVT_FIVE_RONIN;
	    }
	    else if (consecutiveRonins == 4)
	    {
		    roninResult = EVT_FOUR_RONIN;
	    }
	    else if (consecutiveRonins == 3)
	    {
		    roninResult = EVT_THREE_RONIN;
	    }
	    else if (consecutiveRonins == 2)
	    {
		    roninResult = EVT_TWO_RONIN;
	    }

        // Get the payout for the Ronin and check to see if it is a better payout.
        // If so, change the result.
        if ( getPayout(roninResult)->payoutAmount >  getPayout(result)->payoutAmount) 
        {
            result = roninResult;
        }
    }

    // If we found a Koku during evaluation,
    // and we had a winning event then we had
    // a Koku substitution.  Add the note.
    if ( result > EVT_LOSER && bHasKoku ) 
    {
        multiplier *= MULTIPLIER_KOKU_SUBSTITUTE;
        spinline->addNote(NOTE_KOKU_SUBSTITUTION);
    }

    // If we found one or more Ronin during evaluation,
    // and we had a winning event that was not
    // one of the Ronin line wins, then we had
    // a Ronin substitution.  Add the note.
    if ( result > EVT_LOSER && bHasRonin &&
         ( ( result != EVT_TWO_RONIN   ) &&
           ( result != EVT_THREE_RONIN ) &&
           ( result != EVT_FOUR_RONIN  ) &&
           ( result != EVT_FIVE_RONIN  ) 
         ) )
    {
        spinline->addNote(NOTE_RONIN_SUBSTITUTION); 
    }

    rtnresult = (uint8) result;
}



////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::classifyScatters()   
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This is an override of the framework method.  
/// It determines if the current spin merits free spins or scatter payments.
////////////////////////////////////////////////////////////////////////////
void 
RoninGuts::classifyScatters(SlotSpin *spin)
{
	uint8 numRoninIcons             = 0;
    int32 roninScatterPositions     = 0;
    int32 featureScatterPositions   = 0;
	uint8 stop                      = 0;
	uint8 whichItem                 = BLANK;
    uint8 position                  = 0;
    uint8 multiplier                = 1;
    bool  bKokuFound                = false;

	spin->clearScatters();

	// If the current game mode is normal, then
    // check to see if the player won the progressive.
	if (getGameModeId() == MODE_NORMAL ) 
	{
		if ( getRandomProgressiveJackpotResult(spin->getSpinLineCount()) ||
			 m_bForceRandomJackpot)
        {
	        spin->addScatter( 0, 
                              getPayout(EVT_RANDOM_PROGRESSIVE_WINNER)->payoutAmount, 
                              1,
                              true,
                              EVT_RANDOM_PROGRESSIVE_WINNER,
                              0 );
        }
    }

    // Next, check all the icons at each reel/row position.
    // We are looking for Koku coins and Ronin warriors.
    // Both of these contribute to the scatter/feature triggers.
  	for (int reel = 0; reel<5; reel++)
	{
    	for (int row = 0; row < 3; row++)
		{
            position = (row*5)+reel;

			stop = relativeWheelPosition(getGameModeId(), reel, spin->getStops()[reel], row);
			whichItem = getIcon(getGameModeId(), reel, stop)->iconid;
			
			if ( whichItem == RONIN )
			{
				numRoninIcons++;
                roninScatterPositions += 1<<position;

                if ( reel < 2 || (reel > 2 && bKokuFound ) )
                {
                    featureScatterPositions += 1<<position;
                }
            }
            else if ( whichItem == KOKU )
            {
                bKokuFound = true;
                featureScatterPositions += 1<<position;
            }            
		} // row
	} // reel

    // If we found any ronin icons, we might
    // have a scatter winner.  If there is only
    // one ronin, then this will be a scatter miss.
    // We still send a scatter record so the client
    // can highlight the position and/or play 
    // an anticipation sound on the reel.
	if (numRoninIcons > 0 )
    {
        uint8 payouttypeid = EVT_LOSER;

	    switch ( numRoninIcons )
	    {
	       case 1:
                payouttypeid = EVT_SCATTER_NEAR_MISS;
                break;

           case 2: 
                payouttypeid = EVT_TWO_SCATTERED_RONIN;
                break;

	       case 3: 
                payouttypeid = EVT_THREE_SCATTERED_RONIN;
                break;

	       case 4: 
                payouttypeid = EVT_FOUR_SCATTERED_RONIN;
                break;

           case 5: 
                payouttypeid = EVT_FIVE_SCATTERED_RONIN;
                break;
	    };

        int32 nPayout = getPayout(payouttypeid)->payoutAmount * ( spin->getSpinLineCount());

        uint8 noteid = 0;

        // Feature Game 1 has a has a multiplier
        // applied to the scatter win that is equal to 1x
        // the number of spins since the triggering bet.
        if ( getGameModeId() == MODE_FREE_GAME_FEATURE_1 )
        {
            ASSERT(getNumSpinsSinceFirstTrigger());
            noteid = NOTE_AWARD_MULTIPLIED_BY_NUM_FREE_SPINS;
            multiplier *= getNumSpinsSinceFirstTrigger();
        }
        // Feature Game 2 has a has a 3x multiplier
        // applied to the scatter win.
        else if ( getGameModeId() == MODE_FREE_GAME_FEATURE_2 )
        {
            multiplier *= MULTIPLIER_3X;
            noteid = NOTE_3X_AWARD;
        }

        increaseTotalWin(nPayout*multiplier);

        // Let's create a scatter record for the
        // Ronins.
	    SlotScatter* pScatter = spin->addScatter( roninScatterPositions, 
                                                  nPayout*getBetPerLine(),
                                                  multiplier,
                                                  false,
                                                  payouttypeid,
                                                  0 );

        // If the multiplier is > 1 report the note
        // that explains why it is greater than 1.
        if ( multiplier > 1 )
        {
            pScatter->addNote(noteid);
        }

        // If there was a Koku, and at least
        // two Ronin, then the player has triggered
        // a new feature.
        if ( numRoninIcons >= 2 && bKokuFound )
        {
            int32 freespins = 0;
            uint8 freeSpinPayoutTypeId = 0;

            // If the game is currently NOT in a feature 
            // mode, pick a new feature mode.
            if ( getGameModeId() == MODE_NORMAL )
            {
                setFeatureTriggerCount(0);
                setNumSpinsSinceFirstTrigger(0);
				// MVarela, 09/06/2007, 7.1.0, now using a database probability table
				// for getting the random number instead of a hard coded array.
				uint8 nextGameModeId = getRandomValueFromProbabilityTable(PROBABILITY_TABLE_FOR_GAME_MODES);
                setGameModeId( nextGameModeId );
            }

            // Increment the number of times the
            // feature has been triggered since
            // the last paid spin.
            setFeatureTriggerCount(getFeatureTriggerCount()+1);

            // When this feature triggers, the player gets a random number
            // of free spins.
            if ( getGameModeId() == MODE_RESPIN_FEATURE )
            {
				// MVarela, 09/06/2007, 7.1.0, now using a database probability table
				// for getting the random number instead of a hard coded array.
				freespins = getRandomValueFromProbabilityTable(PROBABILITY_TABLE_FOR_FREE_SPINS);
                freeSpinPayoutTypeId = EVT_FREE_GAMES_KOKU_FEATURE;
            }
            else if ( getGameModeId() == MODE_FREE_GAME_FEATURE_1 )
            {
                // This feature can only retrigger once.  If it does,
                // award five more free spins.
                if ( getFeatureTriggerCount() <= 2 )
                {
                    freespins = 5;
                    freeSpinPayoutTypeId = EVT_FREE_GAMES_5_GAMES;
                }
            }
            // When this feature triggers or retriggers, award 25 spins.
            else if ( getGameModeId() == MODE_FREE_GAME_FEATURE_2 )
            {
                freespins = 25;
                freeSpinPayoutTypeId = EVT_FREE_GAMES_25_GAMES;
            }

            // Add a scatter record
            // to log the number
            // of free spins awarded.
            if ( freespins > 0 )
            {
                incrementFreeSpins(freespins);

                spin->addScatter( featureScatterPositions, 
                                  0,
                                  0,
                                  false,
                                  freeSpinPayoutTypeId,
                                  freespins );
            }
        }
    }   

    // If we had at least one Koku or one ronin, but not enough
    // of both symbols to trigger a feature, log a feature near miss scatter.
    if ( 
         ( 
           ( 
             ( getGameModeId() == MODE_FREE_GAME_FEATURE_1 ) && 
             ( getFeatureTriggerCount() <= 2 ) 
           ) ||
           ( getGameModeId() != MODE_FREE_GAME_FEATURE_1 ) 
         ) &&
         ( 
           ( numRoninIcons < 2 || !bKokuFound ) && 
           ( featureScatterPositions > 0 ) 
         )
       )
    {
            spin->addScatter( featureScatterPositions, 
                              0,
                              0,
                              false,
                              EVT_FEATURE_NEAR_MISS,
                              0 );
    }

}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::isValidGuts()   
///
/// @return	 bool8      True if gut is valid.    
///
/// This helper method is used to the current gut ID is valid.
////////////////////////////////////////////////////////////////////////////
bool8 
RoninGuts::isValidGuts()
{
    return (getGutsId() >= 100 && getGutsId() < 199);
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )   
///
/// @param	 nGameModeMultiplier    Returns the new multiplier based on the game mode.
/// @param   nNoteId                Returns the note Id associated with the game mode.
///
/// This helper method is get the game mode note and multiplier.
////////////////////////////////////////////////////////////////////////////
void 
RoninGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )
{
    // Feature Game 1 has a has a multiplier
    // applied to the scatter win that is equal to 1x
    // the number of spins since the triggering bet.
    if ( getGameModeId() == MODE_FREE_GAME_FEATURE_1 )
    {
        ASSERT(getNumSpinsSinceFirstTrigger());
        nGameModeMultiplier *= getNumSpinsSinceFirstTrigger();
        nNoteId = NOTE_AWARD_MULTIPLIED_BY_NUM_FREE_SPINS;
    }
    // Feature Game 2 has a has a 3x multiplier
    // applied to the scatter win.
    else if ( getGameModeId() == MODE_FREE_GAME_FEATURE_2 )
    {
        nGameModeMultiplier *= MULTIPLIER_3X;
        nNoteId = NOTE_3X_AWARD;
    }    
    else
    {
        SlotGuts::getGameModeMultiplierAndNoteId( nGameModeMultiplier, nNoteId );
    }
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::setFeatureTriggerCount(int32 nFeatureTriggerCount) 
///
/// @param   nFeatureTriggerCount    The number of times the feature has been triggered.
///
/// This is an accessor to return the number of times the feature has been triggered.
////////////////////////////////////////////////////////////////////////////
void 
RoninGuts::setFeatureTriggerCount(int32 nFeatureTriggerCount)
{
    m_subGame->update(SGDIDX_FEATURE_TRIGGER_COUNT,nFeatureTriggerCount);
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::getFeatureTriggerCount() const 
///
/// @return  int32   The number of times the feature has been triggered.
///
/// This is a mutator method to change the number of times the feature has been triggered.
////////////////////////////////////////////////////////////////////////////
int32 
RoninGuts::getFeatureTriggerCount() const
{
    return m_subGame->getData(SGDIDX_FEATURE_TRIGGER_COUNT);
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::setNumSpinsSinceFirstTrigger(int32 nNumSpinsSinceFirstTrigger) 
///
/// @param   nFeatureTriggerCount    The number of spins since the first trigger..
///
/// This is an accessor to return the number of spins since the first trigger..
////////////////////////////////////////////////////////////////////////////
void 
RoninGuts::setNumSpinsSinceFirstTrigger(int32 nNumSpinsSinceFirstTrigger)
{
    m_subGame->update(SGDIDX_NUM_SPINS_SINCE_FIRST_TRIGGER,nNumSpinsSinceFirstTrigger);
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::getNumSpinsSinceFirstTrigger() const   
///
/// @return  int32   The number of spins since the first trigger.
///
/// This is a mutator method to change the number of spins since the first trigger.
////////////////////////////////////////////////////////////////////////////
int32 
RoninGuts::getNumSpinsSinceFirstTrigger() const
{
    return m_subGame->getData(SGDIDX_NUM_SPINS_SINCE_FIRST_TRIGGER);
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 RoninGuts::preSpinClassifyResults()   
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This method is overridden when running in debug spins mode to alter the reel stops.
////////////////////////////////////////////////////////////////////////////
void RoninGuts::preSpinClassifyResults( SlotSpin* spin )
{
	SlotGuts::preSpinClassifyResults(spin);

    if ( getGameModeId() == MODE_FREE_GAME_FEATURE_1 )
    {
        setNumSpinsSinceFirstTrigger(getNumSpinsSinceFirstTrigger()+1);
    }
    else if ( getGameModeId() == MODE_RESPIN_FEATURE )
    {
        // In respin mode, we hard code the stop for the middle
        // reel to be position 35.  The reel set for this feature
        // is modified to put three Koku coins starting at position
        // 35.  By overriding this stop we allow the classification
        // code to work as it normally does without any special 
        // casing based on game mode.
        uint8* pOverrideStops = const_cast<uint8*>(spin->getStops());
        pOverrideStops[2] = 35;
    }

}
